package ie.ul.breakthrough;

import java.util.ArrayList;

/** Breakthrough game/board class */
public class Breakthrough {

	public final static int MIN_BOARD_SIZE = 5; // minimum allowed board size
												// (not possible to have fewer
												// rows)
	public final static int MAX_BOARD_SIZE = 8; // upper size limit for this
												// assignment
												// (possible to have bigger
												// boards though)
	protected int size; // size of board
	protected char[][] board; // game board, stored as chars, '.' for empty, 'B'
								// for black
								// and 'W' for white

	public Breakthrough(int aSize) {
		reset(aSize);
	}

	/** Copy constructor, useful for making a separate copy of an existing board */
	public Breakthrough(Breakthrough bt) {
		this(bt.size());
		board = bt.getBoardCopy();
	}

	/** Setter for size */
	public void setSize(int aSize) {
		if (aSize < MIN_BOARD_SIZE)
			throw new BreakthroughException("board size must be at least : "
					+ MIN_BOARD_SIZE);
		if (aSize > MAX_BOARD_SIZE)
			throw new BreakthroughException(
					"board size must be no more than : " + MAX_BOARD_SIZE);
		size = aSize;
	}

	/**
	 * This resets the board to its start state using a different size than the
	 * last game
	 */
	public void reset(int n) {
		setSize(n);
		reset();
	}

	/** This resets the board to its start state */
	public void reset() {
		char value;
		board = new char[size][size];
		for (int row = 0; row < size; row++) {
			if (row < 2) // fill top 2 rows with black
				value = 'B';
			else if (row >= size - 2) // fill final 2 rows with white
				value = 'W';
			else
				value = '.'; // else empty square
			for (int col = 0; col < size; col++)
				board[row][col] = value;
		}
	}

	/** Returns the size of the board as an int */
	public int size() {
		return size;
	}

	/** This returns a string that describes a board */
	public String boardToString(char[][] aBoard, int aSize) {
		String result = "  ";
		int row, col;

		for (col = 0; col < aSize; col++)
			result += col + " ";
		result += "\n";

		for (row = 0; row < aSize; row++) {
			result += row + " ";
			for (col = 0; col < aSize; col++) {
				result += board[row][col] + " ";
			}
			result += "\n";
		}

		return result;

	}

	/** This returns a string that describes the board */
	public String boardToString() {
		return boardToString(board, size);
	}

	/** Gives a textual representation of the board */
	public String toString() {
		return boardToString();
	}

	/**
	 * This returns a char representing the occupant of the specified position
	 * on the board.
	 */
	public char positionOccupant(int row, int col) {
		if (!valid(row, col))
			throw new BreakthroughException("Invalid position!");
		return board[row][col];
	}

	/**
	 * Returns true if the given row and col represent a valid location on the
	 * board.
	 */
	public boolean valid(int row, int col) {
		return (row >= 0 && col >= 0 && row < size && col < size);
	}

	/**
	 * Returns true if the given row and col represent a valid location on the
	 * board and that location contains the given symbol.
	 */
	public boolean contains(int row, int col, char symbol, char[][] workingBoard) {
		return (valid(row, col) && workingBoard[row][col] == symbol);
	}

	/** Returns the number of instances of the symbol on the board */
	public int countSymbol(char symbol) {
		int count = 0;
		for (int row = 0; row < size; row++)
			for (int col = 0; col < size; col++)
				if (board[row][col] == symbol)
					count++;
		return count;
	}

	/**
	 * Given a player symbol, returns the opponent's symbol, 'B' for black, or
	 * 'W' for white
	 */
	public char opponent(char player) {
		if (player == 'B')
			return 'W';
		else
			return 'B';
	}

	/**
	 * This generates a list of all possible moves from the current board
	 * position
	 */
	public ArrayList<BreakthroughMove> generateMoves(char player) {
		ArrayList<BreakthroughMove> moveList = new ArrayList<BreakthroughMove>(); // list
																					// to
																					// store
																					// the
																					// moves
		for (int row = 0; row < size; row++)
			for (int col = 0; col < size; col++) // run through all positions
			{
				if (player == 'W' && board[row][col] == player) // if player is
																// white and at
																// this position
				{
					// check 3 possible moves for white
					BreakthroughMove moveLeftDiag = new BreakthroughMove(row,
							col, row - 1, col - 1);
					BreakthroughMove moveRightDiag = new BreakthroughMove(row,
							col, row - 1, col + 1);
					BreakthroughMove moveForward = new BreakthroughMove(row,
							col, row - 1, col);
					// if legal add them to the list
					if (legalMove(board, player, moveLeftDiag) == null)
						moveList.add(moveLeftDiag);
					if (legalMove(board, player, moveRightDiag) == null)
						moveList.add(moveRightDiag);
					if (legalMove(board, player, moveForward) == null)
						moveList.add(moveForward);
				} else if (player == 'B' && board[row][col] == player) // if
																		// player
																		// is
																		// black
																		// and
																		// at
																		// this
																		// position
				{
					// check 3 possible moves for black
					BreakthroughMove moveLeftDiag = new BreakthroughMove(row,
							col, row + 1, col + 1);
					BreakthroughMove moveRightDiag = new BreakthroughMove(row,
							col, row + 1, col - 1);
					BreakthroughMove moveForward = new BreakthroughMove(row,
							col, row + 1, col);
					// if legal add them to the list
					if (legalMove(board, player, moveLeftDiag) == null)
						moveList.add(moveLeftDiag);
					if (legalMove(board, player, moveRightDiag) == null)
						moveList.add(moveRightDiag);
					if (legalMove(board, player, moveForward) == null)
						moveList.add(moveForward);
				}
			}
		return moveList;
	}

	/** Checks the board to see if a player has just won */
	public boolean isGameOver() {
		int col;

		if (countSymbol('W') == 0 || countSymbol('B') == 0) {
			return true;
		}

		for (col = 0; col < size; col++)
			if (board[0][col] == 'W') // see if any of White's pieces in top row
				return true;
		for (col = 0; col < size; col++)
			if (board[size - 1][col] == 'B') // see if any of Black's pieces in
												// bottom row
				return true;
		return false; // if get to here game not over
	}

	/**
	 * This makes a copy of the current board, tries the move on it, and if the
	 * move is valid, copies the board back over the game board.
	 */
	public void makeMove(char player, BreakthroughMove move) {
		// Updates the current board with the next board created by the given
		// move.
		board = nextBoard(board, player, move);
		if (isGameOver()) // see if that move just won the game
			move.wonGame(); // if so set move to winning move
	}

	/** Creates and returns a copy of the char[][] array holding the board */
	public char[][] getBoardCopy() {
		int row, col;
		char[][] boardCopy = new char[size][size]; // allocate 2d array
		for (row = 0; row < size; row++)
			for (col = 0; col < size; col++)
				// copy the elements
				boardCopy[row][col] = board[row][col];
		return boardCopy;
	}

	/**
	 * Given a move for a particular player from (r1,c1) to (r2,c2) this
	 * executes the move on a copy of the current breakthrough board. It will
	 * raise a BreakthroughException if the move is invalid. It returns the copy
	 * of the board with the new move, and does not change the given board.
	 */
	public char[][] nextBoard(char[][] board, char player, BreakthroughMove move) {
		char[][] nextBoard;

		String warningMessage = legalMove(board, player, move); // warningMessage
																// will be null
																// if move is
																// legal

		if (warningMessage != null) // if invalid move
			throw new BreakthroughException(warningMessage);

		// duplicate existing board
		nextBoard = getBoardCopy();

		int r1 = move.getRow1();
		int c1 = move.getCol1();
		int r2 = move.getRow2();
		int c2 = move.getCol2();

		// make move on duplicated board
		nextBoard[r2][c2] = nextBoard[r1][c1];
		nextBoard[r1][c1] = '.';

		return nextBoard;

	}

	/**
	 * Checks if given move is legal. Returns null if it is. Otherwise returns a
	 * String containing a warning message.
	 */
	public String legalMove(char[][] aBoard, char player, BreakthroughMove move) {
		int r1 = move.getRow1();
		int c1 = move.getCol1();
		int r2 = move.getRow2();
		int c2 = move.getCol2();

		if (!valid(r1, c1) || !valid(r2, c2))
			return "Invalid square!";
		if (aBoard[r1][c1] != player)
			return "Wrong player for this move!";

		if (player == 'B') // black (moving down board, (0,0) coordinate is top
							// left )
		{
			if (r2 != r1 + 1) // all moves must move to next row
				return "Must move forward one row!";
		} else if (player == 'W') // white (moving up board)
		{
			if (r2 != r1 - 1) // all moves must move to next row
				return "Must move forward one row!";
		} else
			return "Don't recognize player: " + player;

		if (c2 < c1 - 1 || c2 > c1 + 1)
			return "Can only move straight-ahead or diagonally one space forward!";

		if (c2 == c1 && aBoard[r2][c2] != '.') // trying to move straight-ahead
												// but space not empty
			return "Cannot move straight-ahead into an occupied square";

		if (c2 != c1 && aBoard[r2][c2] == player)
			return "Cannot move onto one's own piece";

		return null; // move OK
	}

	/** Plays one game with the specified players */
	public char playOneGame(Player p1, Player p2, boolean show) {
		reset();
		p1.initialize('B'); // player one always black (at top of board)
		p2.initialize('W'); // player two always white (at bottom of board)
		BreakthroughMove move;

		if (Math.random() < 0.5) // with 50% probability, player 1 (Black) goes
									// first
		{
			System.out.println(p1.name + " (B) vs " + p2.name + " (W)"); // player
																			// 1
																			// goes
																			// first

		} else // otherwise player 2 (White) goes first
		{
			System.out.println(p2.name + " (W) vs " + p1.name + " (B)"); // player
																			// 2
																			// goes
																			// first

			if (show) {
				System.out.println(this); // prints out board
				System.out.println("Player 2: " + p2.name + " (W's) turn ");
			}

			move = p2.makeMove(this);
			if (move.isLostGame()) {
				System.out.println("Game Over");
				return 'B';
			}
			try {
				makeMove('W', move);
				if (move.isWonGame()) {
					System.out.println(this);
					return 'W'; // 'W' has won
				}
			} catch (BreakthroughException e) {
				System.out.println("Game over: Invalid move by Player 2: "
						+ p2.name + " (W)");
				System.out.println(move);
				System.out.println(this);
				return 'B';
			}

		}

		while (true) {

			// player 1 (black's move)
			if (show) {
				System.out.println(this); // prints out board
				System.out.println("Player 1: " + p1.name + " (B's) turn ");

			}

			move = p1.makeMove(this);
			if (move.isLostGame()) {
				System.out.println("Game Over");
				return 'W';
			}
			try {
				makeMove('B', move);
				if (move.isWonGame()) {
					System.out.println(this);
					return 'B'; // 'B' has won
				}
			} catch (BreakthroughException e) {
				System.out.println("Game over: Invalid move by Player 1: "
						+ p1.name + " (B)");
				System.out.println(move);
				System.out.println(this);
				return 'W';
			}

			// player 2 (white's move)
			if (show) {
				System.out.println(this); // prints out board
				System.out.println("Player 2: " + p2.name + " (W's) turn ");

			}

			move = p2.makeMove(this);
			if (move.isLostGame()) {
				System.out.println("Game Over");
				return 'B';
			}
			try {
				makeMove('W', move);
				if (move.isWonGame()) {
					System.out.println(this);
					return 'W'; // 'W' has won
				}

			} catch (BreakthroughException e) {
				System.out.println("Game over: Invalid move by Player 2: "
						+ p2.name + " (W)");
				System.out.println(move);
				System.out.println(this);
				return 'B';
			}

		}
	}

	/** Plays n games of Breakthrough with the specified players */
	public void playNGames(Player p1, Player p2, int n, boolean show) {
		Player first = p1;
		Player second = p2;
		char winner;
		int p1_wins = 0;
		int p2_wins = 0;

		first.resetWins();
		second.resetWins();

		for (int i = 0; i < n; i++) {
			System.out.println("Game: " + i);
			winner = playOneGame(first, second, show);
			if (winner == 'B') {
				p1_wins++;
				first.won();
				System.out.println("Player 1: " + first.name + " (B) wins");
			} else {
				p2_wins++;
				second.won();
				System.out.println("Player 2: " + second.name + " (W) wins");
			}
		}
		System.out.println("Total wins p1: " + p1_wins + "\nTotal wins p2: "
				+ p2_wins);
	}

}